package org.fujene.memman;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;

import org.fujene.memcon.IndexDisk;
import org.fujene.memcon.LocalIndexMemory;
import org.fujene.structure.Constants;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.LookupPointer;
import org.fujene.structure.Result;
import org.fujene.structure.SearchedTerm;
import org.fujene.structure.Term;
import org.fujene.structure.TermList;
import org.fujene.term.Chopper;
import org.fujene.term.SenChopper;
import org.fujene.term.WordBigramChopper;

/**
 * <p>
 * メモリ・ディスクを横断的に管轄します。 共通に読み出し・書き込みを制御します。
 * </p>
 * また、トランザクション管理のもと、一貫性を維持します。
 */
public class LocalMemoryManager {
	private static final int DIGLENGTH = Constants.SKIPPTR_INTERVAL;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;

	// メモリ・ディスク本体
	private LocalIndexMemory memory = null;
	private IndexDisk disk = null;

	public boolean stopFlag = false;

	public boolean debugFlag = false;
	public boolean debugFlag2 = false;

	private Chopper chopper = null;

	private int indexcounter = 0;
	// private int termcounter = 0;
	private int searchcounter = 0;

	public LocalMemoryManager() {
		this(1048576, 64, "./testDir/");
	}

	public LocalMemoryManager(String dir) {
		this(1048576, 64, dir);
	}

	// 初期化
	public LocalMemoryManager(int blockSize, int blockLines, String dirPath) {
		this(blockSize, blockLines, dirPath, false);
	}

	public LocalMemoryManager(int blockSize, int blockLines, String dirPath,
			boolean isDebugMode) {
		disk = new IndexDisk(dirPath);
		memory = new LocalIndexMemory(blockSize, blockLines, disk);

		if (isDebugMode) {
			System.out.println("Debug Mode.");
			debugFlag = isDebugMode;
		}

		// ↓解析マシンはここで変更！
		chopper = new SenChopper();
		//chopper = new WordBigramChopper();

		System.out.println("Memory manager started.");

	}

	// 検索
	public Result search(IndexQueue newQuery)
			throws UnsupportedEncodingException {
		long time = 0;
		if (newQuery.option == 1)
			time = System.nanoTime();
		// 1-1. 形態素解析
		TermList rawTerms = chopper.chop(newQuery.value);

		// クエリが2文字以上なら1文字だけは検索しない
		TermList terms = null;
		if (newQuery.value.length() > 1) {
			terms = new TermList(rawTerms.size());
			for (Term str : rawTerms) {
				if (str.str().length() != 1)
					terms.add(str);
			}
		} else {
			terms = rawTerms;
		}

		if (newQuery.option == 1)
			System.out.println("Chop\t\t: "
					+ ((System.nanoTime() - time) / 1000.0) + " microsec.");
		Result results = null;
		ArrayList<LookupPointer> finalptrList = new ArrayList<LookupPointer>();
		// 暫定的に10単語で打ち切り
		LookupPointer[] invIndexPtrs = new LookupPointer[(terms.size() > 10 ? 10
				: terms.size())];
		boolean isZeroResult = false;

		int counter = 0;
		for (Term term : terms) {
			// クエリが2文字以上→1文字の単語は検索しない
			// 1. 辞書みる
			if (newQuery.option == 1)
				time = System.nanoTime();
			SearchedTerm newDic = lookupTerm(term.str(), 0);
			if (newQuery.option == 1)
				System.out.println("Lookup " + (counter + 1) + "\t: "
						+ ((System.nanoTime() - time) / 1000.0) + " microsec.");
			if (newQuery.option == 1)
				time = System.nanoTime();
			if (newDic.getPtr() == null) {
				// 辞書にない！ -> 終了
				isZeroResult = true;
				finalptrList.clear();
				break;
			}
			if (newQuery.option == 1)
				System.out.println("Get ptr. " + (counter + 1) + "\t: "
						+ ((System.nanoTime() - time) / 1000.0) + " microsec.");

			if (newQuery.option == 1)
				time = System.nanoTime();
			invIndexPtrs[counter] = getInvIndex(newDic.getPtr());
			if (newQuery.option == 1)
				System.out.println("Cont. List " + (counter + 1) + "\t: "
						+ ((System.nanoTime() - time) / 1000.0) + " microsec.");

			if (invIndexPtrs[counter] == null) {
				isZeroResult = true;
				break;
			}
			if (++counter >= 10)
				break;
		}
		if (newQuery.option == 1)
			time = System.nanoTime();
		// 最後にインターセクション
		if (!isZeroResult) {
			if (counter > 1)
				finalptrList = intersection(invIndexPtrs, 100000);
			else
				finalptrList = getContentList(invIndexPtrs[0], 0, 100000);
		}
		if (newQuery.option == 1)
			System.out.println("Intersection\t: "
					+ ((System.nanoTime() - time) / 1000.0) + " microsec.");

		// 内容いくつか出す
		if (newQuery.option == 1)
			time = System.nanoTime();
		results = new Result(100);
		try {
			results.setArray(getContentsArray(finalptrList, 0, 100));
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (newQuery.option == 1)
			System.out.println("Content\t\t: "
					+ ((System.nanoTime() - time) / 1000.0) + " microsec.");

		if (newQuery.option == 1)
			System.out.println("Result: " + finalptrList.size() + " articles.");

		this.searchcounter++;

		return results;
	}

	public void index(IndexQueue newIndex) throws UnsupportedEncodingException {
		// 1. 本体しまう
		LookupPointer contentPtr = storeContent(newIndex.value);
		// 形態素解析
		TermList terms = chopper.chop(newIndex.value);
		// 2. 辞書あるか調べる
		for (Term term : terms) {
			// 3. 辞書しまう(あれば追加しない)
			SearchedTerm newDic = storeDictionary(term.str());
			// 4. DocIDリストしまう
			storeInvIndex(newDic.getPtr(), contentPtr);
		}

		this.indexcounter++;
		if (this.indexcounter % 100000 == 0)
			System.gc();
	}

	public int getCounter() {
		try {
			return memory.getContentCount();
		} catch (Exception e) {
			return -1;
		}
	}

	public int getTerms() {
		try {
			return memory.getTermCount();
		} catch (Exception e) {
			return -1;
		}
	}

	public int getSearchcounter() {
		return searchcounter;
	}

	public void getMemoryList() {
		memory.showMemoryElapsed();
	}

	public void initializeMemory() {
		try {
			memory.readMemory();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void saveMemory() {
		memory.saveMemories();
	}

	public int getSkipPointerLen() {
		return memory.getSkipPointerLength(MEM_SKIPPTR);
	}

	private SearchedTerm lookupTerm(String query, int valueType) {
		// スキップポインタを見る
		int skipPointerLength = memory.getSkipPointerLength(MEM_SKIPPTR);
		LookupPointer dicPtr = null;
		LookupPointer nextPtr = null;
		SearchedTerm storedTerm = null;
		SearchedTerm returnTerm = null;
		int i;
		int skipLevel = 0;
		int skipVal = 1;
		if (skipPointerLength > 1) {
			skipLevel = (int) (Math.log(skipPointerLength - 1) / Math
					.log(DIGLENGTH));
			skipVal = (int) Math.pow(DIGLENGTH, skipLevel);
		}
		for (i = 0; i < skipPointerLength; i += skipVal) {
			dicPtr = memory.lookupSkipPointer(i, MEM_SKIPPTR);
			// ポインタなし
			if (dicPtr == null)
				break;

			storedTerm = memory.lookupDictionary(dicPtr, valueType);
			storedTerm.setSkipPtrIndex(i);
			int comp = storedTerm.getTerm().compareTo(query);
			if (comp > 0) {
				// storeしてるほうが上 -> これより前
				skipLevel--;
				if (skipLevel == -1 || i == 0) {
					i--;
					break;
				}
				i -= skipVal;
				skipVal = (int) Math.pow(DIGLENGTH, skipLevel);

			} else if (comp == 0) {
				// まさにこの単語
				return storedTerm;
			}

			// 後ろを見る
			if (i + skipVal >= skipPointerLength) {
				do {
					skipLevel--;
					if (skipLevel == -1) {
						skipVal = 1;
						break;
					}
					skipVal = (int) Math.pow(DIGLENGTH, skipLevel);
				} while (i + skipVal >= skipPointerLength);
			}
		}
		if (i == -1) {
			// 一番先頭！ -> storedTermに意味はなくなる
			// 次の単語
			nextPtr = storedTerm.getPtr();
			storedTerm = null;
		} else {
			// もう一度スキップポインタ取り出し
			if (i == skipPointerLength)
				i--;
			dicPtr = memory.lookupSkipPointer(i, MEM_SKIPPTR);
			storedTerm = memory.lookupDictionary(dicPtr, valueType);
		}

		// 辞書順検索
		if (storedTerm != null && returnTerm == null) {
			LookupPointer prevPtr = storedTerm.getPtr();
			SearchedTerm nextTerm = memory.lookupDictionary(storedTerm.getNext(),
					valueType);
			int count = 0;
			while (nextTerm != null) {
				int comp = nextTerm.getTerm().compareTo(query);
				if (comp > 0) {
					// nextTermが上 -> 辞書になかった
					boolean addSkipPtr = count >= DIGLENGTH ? true : false;
					returnTerm = new SearchedTerm(query, null, prevPtr,
							nextTerm.getPtr(), null, i, addSkipPtr);
					break;
				} else if (comp == 0) {
					// ↓はいらないかも
					nextTerm.setPrev(prevPtr);
					return nextTerm;
				} else {
					prevPtr = nextTerm.getPtr();
					nextTerm = memory.lookupDictionary(nextTerm.getNext(),
							valueType);
				}
				count++;
			}

			if (returnTerm == null) {
				// 最後の単語だった
				boolean addSkipPtr = count >= DIGLENGTH ? true : false;
				returnTerm = new SearchedTerm(query, null, prevPtr, null, null, i,
						addSkipPtr);
			}
		} else {
			// 一番先頭(i = -1) or 最初の単語(i = 0)
			returnTerm = new SearchedTerm(query, null, null, nextPtr, null, i, false);
		}

		return returnTerm;
	}

	private void makeSkipPtr(SearchedTerm term, int type) {
		int index = term.getSkipPtrIndex();

		if (index == -1) {
			// 最初の部分書き換え
			memory.storeSkipPointer(0, type, term.getPtr(), true);
			if (type == MEM_SKIPPTR)
				refreshSkipPtrDict();
		} else {
			if (term.isMakeSkip()) {
				// 追加
				memory.storeSkipPointer(index + 1, type, term.getPtr(), false);
			}
		}
	}

	// 1番目のスキップポインタに固まらない対策
	private void refreshSkipPtrDict() {
		LookupPointer threshold = memory.lookupSkipPointer(1, MEM_SKIPPTR);
		LookupPointer nextPtr = memory.lookupSkipPointer(0, MEM_SKIPPTR);
		LookupPointer prevPtr = null;
		int count = 0;
		if (threshold == null) {
			while (nextPtr != null) {
				prevPtr = nextPtr;
				nextPtr = memory.lookupDictionaryNext(nextPtr);
				count++;
			}
		} else {
			while (threshold.equals(nextPtr)) {
				prevPtr = nextPtr;
				nextPtr = memory.lookupDictionaryNext(nextPtr);
				count++;
			}
		}

		if (count >= DIGLENGTH) {
			memory.storeSkipPointer(1, MEM_SKIPPTR, prevPtr, false);
		}
	}

	// 辞書をストア
	private SearchedTerm storeDictionary(String query)
			throws UnsupportedEncodingException {
		SearchedTerm newTerm = lookupTerm(query, 0);

		if (newTerm.getPtr() == null) {
			// 単語追加
			LookupPointer newTermPtr = memory.storeDictionary(
					newTerm.getTerm(), newTerm.getNext());
			newTerm.setPtr(newTermPtr);

			// ポインタ接続
			if (newTerm.getPrev() != null)
				memory.storeDictionaryPointer(newTerm.getPrev(), newTermPtr);

			// スキップポインタ追加
			makeSkipPtr(newTerm, MEM_SKIPPTR);
		}

		return newTerm;
	}

	private ArrayList<LookupPointer> getContentList(LookupPointer dicPtr,
			int from, int len) {
		ArrayList<LookupPointer> result = new ArrayList<LookupPointer>(len);

		LookupPointer invIndexPtr = memory.lookupDictionaryInvIndex(dicPtr);
		LookupPointer[] eachPtrs = null;
		int counter = 0;
		while (invIndexPtr != null) {
			eachPtrs = memory.lookupInvertIndex(invIndexPtr);
			if (counter >= from && eachPtrs[1] != null)
				result.add(eachPtrs[1]);

			invIndexPtr = eachPtrs[0];

			if (++counter >= from + len)
				break;
		}

		return result;
	}

	private void storeInvIndex(LookupPointer dicPtr, LookupPointer contentsPtr) {
		// 前方ポインタ取得
		LookupPointer prevPtr = memory.lookupDictionaryInvIndex(dicPtr);

		// 転置インデックス書き込み
		LookupPointer invIndexPtr = memory.storeInvertIndex(prevPtr,
				contentsPtr);

		// 辞書ポインタ更新
		memory.storeDictionaryDocIDPtr(dicPtr, invIndexPtr);
	}

	// 本文をメモリにストア -> ポインタを返す
	private LookupPointer storeContent(String content) {
		return memory.storeContents(content);
	}

	// 転置インデックスの「先頭」を取り出す
	private LookupPointer getInvIndex(LookupPointer dicPtr) {
		return memory.lookupDictionaryInvIndex(dicPtr);
	}

	// 多重インターセクション
	private ArrayList<LookupPointer> intersection(LookupPointer[] invindex,
			int length) {
		try {
			ArrayList<LookupPointer> finalPtr = new ArrayList<LookupPointer>(
					length + 1);

			int ptrlen = invindex.length;
			LookupPointer[] currentPtrs = new LookupPointer[ptrlen];
			LookupPointer[] prevPtrs = new LookupPointer[ptrlen];
			LookupPointer[] tmpPtrs = null;
			int counter = 0;
			boolean samePtr = false;
			boolean isDone = false;
			int newestIndex;
			LookupPointer newestPtr;
			int comps;

			// 計算量: O(n)
			for (int i = 0; i < ptrlen; i++) {
				tmpPtrs = memory.lookupInvertIndex(invindex[i]);
				currentPtrs[i] = tmpPtrs[1];
				prevPtrs[i] = tmpPtrs[0];
			}

			while (counter < length) {
				samePtr = true;
				newestIndex = 0;
				newestPtr = currentPtrs[0];
				// ポインタの比較
				for (int i = 1; i < ptrlen; i++) {
					comps = newestPtr.compareTo(currentPtrs[i]);
					if (comps < 0) {
						newestIndex = i;
						newestPtr = currentPtrs[i];
						samePtr = false;
					} else if (samePtr && comps > 0) {
						samePtr = false;
					}
				}

				if (samePtr) {
					// 一致 全ポインタ進め
					finalPtr.add(newestPtr);
					counter++;
					for (int i = 0; i < ptrlen; i++) {
						if (prevPtrs[i] == null) {
							// もう進めない！
							isDone = true;
							break;
						}
						tmpPtrs = memory.lookupInvertIndex(prevPtrs[i]);
						if(tmpPtrs == null)
							System.out.println("NULL! at " + prevPtrs[i].toString());
						currentPtrs[i] = tmpPtrs[1];
						prevPtrs[i] = tmpPtrs[0];
					}
				} else {
					// 一番新しいものを進める
					if (prevPtrs[newestIndex] != null) {
						tmpPtrs = memory
								.lookupInvertIndex(prevPtrs[newestIndex]);
						if(tmpPtrs == null)
							System.out.println("NULL! at " + prevPtrs[newestIndex].toString());
						currentPtrs[newestIndex] = tmpPtrs[1];
						prevPtrs[newestIndex] = tmpPtrs[0];
					} else {
						// これ以上揃いようがない
						isDone = true;
					}
				}

				if (isDone)
					break;
			}

			return finalPtr;
		} catch (Exception e) {
			
			e.printStackTrace();
			return null;
		}
	}

	// 本文を取得
	private String[] getContentsArray(ArrayList<LookupPointer> contentPtr,
			int from, int length) {
		assert contentPtr.size() >= 0;

		int counter = 0;
		int arrayCounter = 0;
		String[] retList = new String[length];
		String currentStr = null;

		for (LookupPointer ptr : contentPtr) {
			if (counter++ < from)
				continue;
			currentStr = memory.lookupContents(ptr);
			if (currentStr != null) {
				retList[arrayCounter++] = currentStr;
				if (arrayCounter == length)
					break;
			}
		}

		return Arrays.copyOfRange(retList, 0, arrayCounter);
	}
}
